package gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.rmi.RemoteException;

import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import server.AdminServer;
import server.Server.Action;
import world.Critter;

public class ControlPanel extends JPanel {
	
	private AdminServer adminServer;
	private final WorldViewPanel view;
	private JRadioButton[] actionButtons;
	private ButtonGroup actionGroup;
	private JButton playButton;
	private JButton incrementButton;
	private JSlider speedSlider;
	private Timer viewRefreshTimer;
	
	// Account Panel buttons
	private ButtonGroup downloadGroup;
	private ButtonGroup uploadGroup;
	private Box downloadBox;
	private Box uploadBox;
	private JButton showUsersButton;
	private JButton showAdminsButton;
	private JButton showUserRequestsButton;
	private JButton showAdminRequestsButton;
	
	private JPanel accountPanel;
	private JPanel worldPanel;
	private JPanel critterPanel;
	
	private boolean playing;
	private final Color bkg = new Color(255,99,71);

	public ControlPanel(AdminServer server, WorldViewPanel worldView) {
		adminServer = server;
		view = worldView;
		view.addControlMouseListener(new WorldViewClickListener());
		
		setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
		accountPanel = new JPanel(new GridLayout(3, 2));
		worldPanel = new JPanel(new GridLayout(0, 1));
		critterPanel = new JPanel(new GridLayout(6, 2));
		
		makeButtons();
		makeAccountButtons();
		
		JPanel aLabelPanel = new JPanel(){
			public Dimension getMaximumSize(){
				return new Dimension(500, 20);
			}
		};
		aLabelPanel.setBackground(bkg);
		aLabelPanel.add(new JLabel("Account Controls"));
		add(aLabelPanel);
		add(accountPanel);
		
		JPanel wLabelPanel = new JPanel(){
			public Dimension getMaximumSize(){
				return new Dimension(500, 20);
			}
		};
		wLabelPanel.setBackground(bkg);
		wLabelPanel.add(new JLabel("World Controls"));
		add(wLabelPanel);
		add(worldPanel);
		
		worldPanel.add(playButton);
		worldPanel.add(new JLabel("Steps per minute:"));
		worldPanel.add(speedSlider);
		worldPanel.add(incrementButton);
		
		JPanel cLabelPanel = new JPanel(){
			public Dimension getMaximumSize(){
				return new Dimension(500, 20);
			}
		};
		cLabelPanel.setBackground(bkg);
		cLabelPanel.add(new JLabel("Critter Controls"));
		add(cLabelPanel);
		add(critterPanel);
		
		for (int i = 0; i < 11; i++)
			critterPanel.add(actionButtons[(i+1)%11]);
		
		try {
			playing = adminServer.isRunning();
		} catch (RemoteException e) {
			JOptionPane.showMessageDialog(this, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
		}
		setBackground(bkg);
		setBorder(new LineBorder(bkg, 10));
	}
	
	private void makeAccountButtons() {
		uploadGroup = new ButtonGroup();
		downloadGroup = new ButtonGroup();
		uploadBox = new Box(BoxLayout.Y_AXIS);
		downloadBox = new Box(BoxLayout.Y_AXIS);

		JRadioButton uploadsOn = new JRadioButton("Uploads On");
		boolean uploadsOK = true;
		try {
			uploadsOK = adminServer.uploadsOn();
		} catch (RemoteException e2) {
			JOptionPane.showMessageDialog(SwingUtilities.getRootPane(ControlPanel.this),
					e2.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
		}
		uploadsOn.setSelected(uploadsOK);
		uploadsOn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					adminServer.setCritterUploads(true);
				} catch (RemoteException e1) {
					JOptionPane.showMessageDialog(SwingUtilities.getRootPane(ControlPanel.this),
							e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
				}
			}
		});
		JRadioButton uploadsOff = new JRadioButton("Uploads Off");
		uploadsOff.setSelected(!uploadsOK);
		uploadsOff.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					adminServer.setCritterUploads(false);
				} catch (RemoteException e1) {
					JOptionPane.showMessageDialog(SwingUtilities.getRootPane(ControlPanel.this),
							e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
				}
			}
		});
		uploadGroup.add(uploadsOn);
		uploadGroup.add(uploadsOff);
		uploadBox.add(uploadsOn);
		uploadBox.add(uploadsOff);

		JRadioButton downloadsOn = new JRadioButton("Downloads On");
		boolean downloadsOK = true;
		try {
			downloadsOK = adminServer.uploadsOn();
		} catch (RemoteException e2) {
			JOptionPane.showMessageDialog(SwingUtilities.getRootPane(ControlPanel.this),
					e2.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
		}
		downloadsOn.setSelected(downloadsOK);
		downloadsOn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					adminServer.setCritterDownloads(true);
				} catch (RemoteException e1) {
					JOptionPane.showMessageDialog(SwingUtilities.getRootPane(ControlPanel.this),
							e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
				}
			}
		});
		JRadioButton downloadsOff = new JRadioButton("Downloads Off");
		downloadsOff.setSelected(!downloadsOK);
		downloadsOff.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					adminServer.setCritterDownloads(false);
				} catch (RemoteException e1) {
					JOptionPane.showMessageDialog(SwingUtilities.getRootPane(ControlPanel.this),
							e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
				}
			}
		});
		downloadGroup.add(downloadsOn);
		downloadGroup.add(downloadsOff);
		downloadBox.add(downloadsOn);
		downloadBox.add(downloadsOff);

		showUsersButton = new JButton("Show Users");
		showUsersButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				new UsersFrame(adminServer);
			}
		});
		showAdminsButton = new JButton("Show Admins");
		showAdminsButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				new AdminsFrame(adminServer);
			}
		});
		showUserRequestsButton = new JButton("Show User Requests");
		showUserRequestsButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				new UserRequestsFrame(adminServer);
			}
		});
		showAdminRequestsButton = new JButton("Show Admin Requests");
		showAdminRequestsButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				new AdminRequestsFrame(adminServer);
			}
		});

		accountPanel.add(uploadBox);
		accountPanel.add(downloadBox);
		accountPanel.add(showUsersButton);
		accountPanel.add(showAdminsButton);
		accountPanel.add(showUserRequestsButton);
		accountPanel.add(showAdminRequestsButton);
	}

	public int getPlaySpeed(){
		return speedSlider.getValue();
	}

	private void makeButtons() {
		playButton = new JButton("Play");
		playButton.addActionListener(new PlayPauseListener());
		incrementButton = new JButton("Next Time Step");
		incrementButton.addActionListener(new IncrementListener());
		String[] actions = {"Program (space)", "Wait (w)", "Forward (up)", "Backward (down)", "Left (left)",
				"Right (right)", "Eat (e)", "Attack (a)", "Grow (g)", "Bud (b)", "Mate (m)"};
		actionButtons = new JRadioButton[11];
		actionGroup = new ButtonGroup();
		for (int i = -1; i < 10; i++){
			actionButtons[i+1] = new JRadioButton(actions[i+1]);
			actionButtons[i+1].addActionListener(new CritterAction(i));
			actionGroup.add(actionButtons[i+1]);
		}
		actionButtons[0].setSelected(true);
		speedSlider = new JSlider(1, 1000);
		speedSlider.setValue(60);
		speedSlider.setLabelTable(speedSlider.createStandardLabels(200));
		speedSlider.setMajorTickSpacing(100);
		speedSlider.setMinorTickSpacing(20);
		speedSlider.setPaintLabels(true);
		speedSlider.setPaintTicks(true);
		speedSlider.addChangeListener(new SpeedSliderListener());
		viewRefreshTimer = new Timer((int)(1.0/speedSlider.getValue() * 60000), new ActionListener(){
			public void actionPerformed(ActionEvent e){
				view.repaint();
			}
		});
		addKeyBoardActions();
	}

	private void addKeyBoardActions() {
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "forward");
		getActionMap().put("forward", actionButtons[Critter.FORWARD + 1].getAction());
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "backward");
		getActionMap().put("backward", actionButtons[Critter.BACKWARD + 1].getAction());
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
		getActionMap().put("left", actionButtons[Critter.LEFT + 1].getAction());
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
		getActionMap().put("right", actionButtons[Critter.RIGHT + 1].getAction());
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0), "eat");
		getActionMap().put("eat", actionButtons[Critter.EAT + 1].getAction());
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), "rest");
		getActionMap().put("rest", actionButtons[Critter.REST + 1].getAction());
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_B, 0), "bud");
		getActionMap().put("bud", actionButtons[Critter.BUD + 1].getAction());
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), "attack");
		getActionMap().put("attack", actionButtons[Critter.ATTACK + 1].getAction());
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0), "mate");
		getActionMap().put("mate", actionButtons[Critter.MATE + 1].getAction());
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_G, 0), "grow");
		getActionMap().put("grow", actionButtons[Critter.GROW + 1].getAction());
	}
	
	public void pauseWorld() throws RemoteException{
		adminServer.pauseSim();
		viewRefreshTimer.stop();
		playing = false;
		playButton.setText("Play");
	}
	
	private void playWorld() throws RemoteException{
		adminServer.startSim();
		viewRefreshTimer.start();
		playing = true;
		playButton.setText("Pause");	
	}
	
	private void togglePlayPause() throws RemoteException{
		if(playing)
			pauseWorld();
		else
			playWorld();
	}
	
	private class PlayPauseListener implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			try {
				togglePlayPause();
			} catch (RemoteException e1) {
				JOptionPane.showMessageDialog(SwingUtilities.getRootPane(ControlPanel.this),
						e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
			}				
		}
	}
	
	private class IncrementListener implements ActionListener {
		public void actionPerformed(ActionEvent e) {
				SwingWorker<Object, Object> stepper = new SwingWorker<Object, Object>(){
				protected Object doInBackground() throws Exception {
					adminServer.simStep();
					return null;
				}
				protected void done(){
					try{
						get();
						SwingUtilities.getRootPane(getParent()).repaint();
					} catch (Exception e1) {
						JOptionPane.showMessageDialog(ControlPanel.this,
								e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
					}
				}
			};
			stepper.execute();
		}
	}
	
	/**
	 * Designed for JSlider, never use with anything else
	 */
	private class SpeedSliderListener implements ChangeListener{
		public void stateChanged(ChangeEvent e) {
			JSlider slider = (JSlider) e.getSource();
			try {
				adminServer.setSimRate((long)(1.0/slider.getValue() * 60000));
				viewRefreshTimer.setDelay((int)(1.0/slider.getValue() * 60000));
			} catch (RemoteException e1) {
				JOptionPane.showMessageDialog(ControlPanel.this,
						e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
			}
		}
	}
	
	private class WorldViewClickListener extends MouseAdapter{
		public void mouseClicked(MouseEvent e){
			if(view.getSelectedCell() != null && view.getSelectedCell().hasCritter()){
				for(JRadioButton b: actionButtons)
					b.setEnabled(true);
		    	SwingWorker<Action, Object> getter = new SwingWorker<Action, Object>(){
					protected Action doInBackground() throws Exception {
						return adminServer.getCritterAction(view.getSelectedCell().critId());
					}
					protected void done(){
						try{
							Action nextAct = get();
							if (nextAct == null) actionButtons[0].setSelected(true);
							else {
								switch (nextAct) {
								case WAIT: actionButtons[1].setSelected(true); break;
								case FORWARD: actionButtons[2].setSelected(true); break;
								case BACKWARD: actionButtons[3].setSelected(true); break;
								case LEFT: actionButtons[4].setSelected(true); break;
								case RIGHT: actionButtons[5].setSelected(true); break;
								case EAT: actionButtons[6].setSelected(true); break;
								case ATTACK: actionButtons[7].setSelected(true); break;
								case GROW: actionButtons[8].setSelected(true); break;
								case BUD: actionButtons[9].setSelected(true); break;
								case MATE: actionButtons[10].setSelected(true); break;
								}
							}
							SwingUtilities.getRootPane(ControlPanel.this).repaint();
						} catch (Exception e1) {
							System.out.println("Something went wrong. 	" + e1);
							JOptionPane.showMessageDialog(ControlPanel.this,
									e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
						}
					}
				};
				getter.execute();
			} else {
				for(JRadioButton b: actionButtons)
					b.setEnabled(false);
			}
		}
	}
	
	class CritterAction extends AbstractAction {
		private Action action;
		
		public Action getCritAction() {
			return action;
		}
		
	    public CritterAction(int command) {
	    	switch(command){
	    	case Critter.REST: action = Action.WAIT; break;
	    	case Critter.FORWARD: action = Action.FORWARD; break;
	    	case Critter.BACKWARD: action = Action.BACKWARD; break;
	    	case Critter.LEFT: action = Action.LEFT; break;
	    	case Critter.RIGHT: action = Action.RIGHT; break;
	    	case Critter.EAT: action = Action.EAT; break;
	    	case Critter.ATTACK: action = Action.ATTACK; break;
	    	case Critter.GROW: action = Action.GROW; break;
	    	case Critter.BUD: action = Action.BUD; break;
	    	case Critter.MATE: action = Action.MATE; break;
	    	default: action = null; break;
	    	}
	    }
	    
	    public void actionPerformed(ActionEvent e) {
	    	SwingWorker<Object, Object> controller = new SwingWorker<Object, Object>(){
				protected Object doInBackground() throws Exception {
					adminServer.control(view.getSelectedCell().critId(), action);
					return null;
				}
				protected void done(){
					try{
						get();
						SwingUtilities.getRootPane(getParent()).repaint();
					} catch (Exception e1) {
						JOptionPane.showMessageDialog(ControlPanel.this,
								e1.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
					}
				}
			};
			controller.execute();
	    }
	}
}
